﻿using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Globalization;
using DaveSexton.Labs;
#if NETFX_CORE
using System.Linq;
#endif

namespace System.Reflection
{
	internal static class ReflectionExtensions
	{
		public static bool TryGetAttributeValue<TAttribute>(this MemberInfo typeOrMember, Func<TAttribute, string> getValue, out string value)
			where TAttribute : Attribute
		{
			Contract.Requires(typeOrMember != null);
			Contract.Requires(getValue != null);
#if !WINDOWS_PHONE_70
			Contract.Ensures(Contract.Result<bool>() == false || !string.IsNullOrWhiteSpace(Contract.ValueAtReturn(out value)));
#else
			Contract.Ensures(Contract.Result<bool>() == false || !string.IsNullOrEmpty(Contract.ValueAtReturn(out value)));
#endif

			// Do not use TypeDescriptor here because for certain attributes it returns defaults even if the attributes aren't
			// present on the specified type.  For example, searching for CategoryAttribute returns an instance of the attribute 
			// with its Category property set to "Misc" even if the type nor any of its base types declared it.
			var attributes = (TAttribute[]) typeOrMember.GetCustomAttributes(typeof(TAttribute), true);

			return TryGetAttributeValue(attributes, getValue, out value);
		}

		public static bool TryGetAttributeValue<TAttribute>(this Assembly assembly, Func<TAttribute, string> getValue, out string value)
			where TAttribute : Attribute
		{
			Contract.Requires(assembly != null);
			Contract.Requires(getValue != null);
#if !WINDOWS_PHONE_70
			Contract.Ensures(Contract.Result<bool>() == false || !string.IsNullOrWhiteSpace(Contract.ValueAtReturn(out value)));
#else
			Contract.Ensures(Contract.Result<bool>() == false || !string.IsNullOrEmpty(Contract.ValueAtReturn(out value)));
#endif

#if NETFX_CORE
			var attributes = assembly.GetCustomAttributes(typeof(TAttribute)).Cast<TAttribute>().ToArray();
#else
			var attributes = (TAttribute[]) assembly.GetCustomAttributes(typeof(TAttribute), false);
#endif

			return TryGetAttributeValue(attributes, getValue, out value);
		}

		private static bool TryGetAttributeValue<TAttribute>(TAttribute[] attributes, Func<TAttribute, string> getValue, out string value)
		{
			Contract.Requires(attributes != null);
			Contract.Requires(getValue != null);
#if !WINDOWS_PHONE_70
			Contract.Ensures(Contract.Result<bool>() == false || !string.IsNullOrWhiteSpace(Contract.ValueAtReturn(out value)));
#else
			Contract.Ensures(Contract.Result<bool>() == false || !string.IsNullOrEmpty(Contract.ValueAtReturn(out value)));
#endif

			if (attributes.Length == 0)
			{
				value = null;
				return false;
			}

			var attribute = (TAttribute) attributes[0];

			if (attribute == null)
			{
				value = null;
				return false;
			}

			value = getValue(attribute);

#if !WINDOWS_PHONE_70
			return !string.IsNullOrWhiteSpace(value);
#else
			return !string.IsNullOrEmpty(value);
#endif
		}

		public static bool TryGetAssemblyTitle(this Assembly assembly, out string title)
		{
			Contract.Requires(assembly != null);
#if !WINDOWS_PHONE_70
			Contract.Ensures(Contract.Result<bool>() == false || !string.IsNullOrWhiteSpace(Contract.ValueAtReturn(out title)));
#else
			Contract.Ensures(Contract.Result<bool>() == false || !string.IsNullOrEmpty(Contract.ValueAtReturn(out title)));
#endif

			if (TryGetAttributeValue<AssemblyProductAttribute>(assembly, a => a.Product, out title))
			{
				title = string.Format(CultureInfo.CurrentCulture, DaveSexton.Labs.Properties.Resources.AssemblyTitleFormat, title);

#if !WINDOWS_PHONE_70
				Contract.Assume(!string.IsNullOrWhiteSpace(title));
#else
				Contract.Assume(!string.IsNullOrEmpty(title));
#endif

				return true;
			}
			else
				return TryGetAttributeValue<AssemblyTitleAttribute>(assembly, a => a.Title, out title);
		}

		public static bool TryGetDisplayName(this MemberInfo typeOrMember, out string displayName)
		{
			Contract.Requires(typeOrMember != null);
#if !WINDOWS_PHONE_70
			Contract.Ensures(Contract.Result<bool>() == false || !string.IsNullOrWhiteSpace(Contract.ValueAtReturn(out displayName)));
#else
			Contract.Ensures(Contract.Result<bool>() == false || !string.IsNullOrEmpty(Contract.ValueAtReturn(out displayName)));
#endif

			return typeOrMember.TryGetAttributeValue<DisplayNameAttribute>(a => a.DisplayName, out displayName);
		}

		public static bool TryGetDescription(this MemberInfo typeOrMember, out string description)
		{
			Contract.Requires(typeOrMember != null);
#if !WINDOWS_PHONE_70
			Contract.Ensures(Contract.Result<bool>() == false || !string.IsNullOrWhiteSpace(Contract.ValueAtReturn(out description)));
#else
			Contract.Ensures(Contract.Result<bool>() == false || !string.IsNullOrEmpty(Contract.ValueAtReturn(out description)));
#endif

			return typeOrMember.TryGetAttributeValue<DescriptionAttribute>(a => a.Description, out description);
		}

		public static bool TryGetCategory(this MemberInfo typeOrMember, out string category)
		{
			Contract.Requires(typeOrMember != null);
#if !WINDOWS_PHONE_70
			Contract.Ensures(Contract.Result<bool>() == false || !string.IsNullOrWhiteSpace(Contract.ValueAtReturn(out category)));
#else
			Contract.Ensures(Contract.Result<bool>() == false || !string.IsNullOrEmpty(Contract.ValueAtReturn(out category)));
#endif

			return typeOrMember.TryGetAttributeValue<CategoryAttribute>(a => a.Category, out category);
		}

		public static bool TryGetExperiment(this MemberInfo typeOrMember, out string displayName)
		{
			Contract.Requires(typeOrMember != null);
#if !WINDOWS_PHONE_70
			Contract.Ensures(Contract.Result<bool>() == false || !string.IsNullOrWhiteSpace(Contract.ValueAtReturn(out displayName)));
#else
			Contract.Ensures(Contract.Result<bool>() == false || !string.IsNullOrEmpty(Contract.ValueAtReturn(out displayName)));
#endif

			return typeOrMember.TryGetAttributeValue<ExperimentAttribute>(a => a.DisplayName, out displayName);
		}
	}
}